#include "Interpreter.hpp"
#include <glob.h>

struct GlobToken
{
	GlobToken(int specialChar);
	GlobToken(const char *text);
	GlobToken(const char *text, int textLen);
	GlobToken(const GlobToken &copy);
	~GlobToken();
	bool operator==(const GlobToken &rhs) const;
	
	int type;
	char *text;
};

GlobToken::GlobToken(int specialChar)
{
	this->type = specialChar;
	this->text = NULL;
}
GlobToken::GlobToken(const char *text)
{
	this->type = 0;
	this->text = strdup(text);
}
GlobToken::GlobToken(const char *text, int textLen)
{
	this->type = 0;
	this->text = (char*)malloc(textLen+1);
	memcpy(this->text, text, textLen);
	this->text[textLen] = 0;
}
GlobToken::GlobToken(const GlobToken &copy)
{
	this->type = copy.type;
	if(copy.text)
		text = strdup(copy.text);
	else
		text = NULL;
}

GlobToken::~GlobToken()
{
	if(text)
		free(text);
}

bool GlobToken::operator==(const GlobToken &rhs) const
{
	if(type != rhs.type)
		return false;
	if(text && strcmp(text, rhs.text))
		return false;
	return true;
}

/////////////////////////////////////////////////////////////////////////////

bool isSpecialGlobChar(int ch, bool quoted)
{
	switch(ch)
	{
		case '/':
		case '\\':
			return true;
		case '*':
		case '?':
		case '[':
		case ']':
			return !quoted;
		default:
			return false;
	}
}

void tokenizeGlob(list<Token>::iterator start, list<Token>::iterator end, vector<GlobToken> &glob)
{
	for(list<Token>::iterator pos=start; pos!=end; pos++)
	{
		bool quoted = pos->quoted;
		unsigned textStart = 0;
		
		for(unsigned jj=0; jj<pos->text.length(); jj++)
		{
			if(isSpecialGlobChar(pos->text[jj], quoted))
			{
				if(textStart < jj) {
					glob.push_back(GlobToken(
						pos->text.c_str() + textStart,
						jj - textStart));
				}
				
				glob.push_back(GlobToken(pos->text[jj]));
				
				textStart = jj+1;
			}
		}
		
		if(textStart < pos->text.length()) {
			glob.push_back(GlobToken(
				pos->text.c_str() + textStart,
				pos->text.length() - textStart));
		}
	}
}

void filenameExpand(list<Token>::iterator start, list<Token>::iterator end, vector<string> &params, Job *context)
{
	if(start==end)
		return;
	
	// Retokenize, treating / and \ as special characters, *?[] as special
	// characters unless quoted, and everything else as literal text.
	vector<GlobToken> globTokens;
	tokenizeGlob(start, end, globTokens);
	
	// Format the glob expression for glob(3)
	// (That is, put a backslash in front of every \, *, ?, [, and ] which our
	// tokenization thinks shouldn't be special.)
	string globstr = "";
	for(unsigned ii=0; ii<globTokens.size(); ii++) {
		if(globTokens[ii].type == 0) {
			for(char *pos=globTokens[ii].text; *pos; pos++)
			{
				if(*pos=='*' || *pos=='?' || *pos=='[' || *pos==']') {
					globstr += '\\';
				}
				globstr += *pos;
			}
		} else if(globTokens[ii].type == '\\') {
			globstr += "\\\\";
		} else {
			globstr += globTokens[ii].type;
		}
	}
	
	// Call glob(3) to do the actual directory traversal
	glob_t globData;
	glob(globstr.c_str(), GLOB_NOCHECK, NULL, &globData);
	
	for(size_t ii=0; ii<globData.gl_pathc; ii++)
		params.push_back(globData.gl_pathv[ii]);
	
	globfree(&globData);
}

TEST_SET(tokenizeGlob)
{
	list<Token> tokens;
	tokens.push_back(Token(TOK_TEXT, "text */a\\[]sdf*", false));
	tokens.push_back(Token(TOK_TEXT, "*/asdf*", true));
	
	vector<GlobToken> globTokens;
	tokenizeGlob(tokens.begin(), tokens.end(), globTokens);
	
	testAssert(globTokens[0] == GlobToken("text "));
	testAssert(globTokens[1] == GlobToken('*'));
	testAssert(globTokens[2] == GlobToken('/'));
	testAssert(globTokens[3] == GlobToken("a"));
	testAssert(globTokens[4] == GlobToken('\\'));
	testAssert(globTokens[5] == GlobToken('['));
	testAssert(globTokens[6] == GlobToken(']'));
	testAssert(globTokens[7] == GlobToken("sdf"));
	testAssert(globTokens[8] == GlobToken('*'));
	testAssert(globTokens[9] == GlobToken("*"));
	testAssert(globTokens[10] == GlobToken('/'));
	testAssert(globTokens[11] == GlobToken("asdf*"));
}

